/*
 * Copyright (c) 2012 Johan Fylling
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights 
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 * THE SOFTWARE.
 */

part of engine;


class FieldOfView {
  static Math.Random  random      = new Math.Random();
  
  final DynamicBody   body        = new DynamicBody();
  final CollidableRay rayOfView   = new CollidableRay();
  final Set<int>      _seenActors = new Set<int>();
  bool                _hasSeen    = false;
  int                 _actorId    = 0;
  
  FieldOfView (num distanceOfView) {
    this.body.collidable = new CollidableCircle(distanceOfView);
  }
  
  CollidableCircle get circleOfView => this.body.collidable as CollidableCircle;
  
  num get distanceOfView => this.circleOfView.radius;
  
  void set distanceOfView (num distance) { this.circleOfView.radius = distance; }
  
  Collection<int> get seenActors => this._seenActors;
  
  void init (Actor actor) {
    this.body.position = actor.body.position;
    this._actorId = actor.id;
    this._seenActors.clear();
    this._hasSeen = false;
  }
  
  Set<int> getSeenActors (CollisionManager collisionManager) {
    if (!this._hasSeen) {
      this._hasSeen = true;
      DebugPainter debugPainter = (ServiceManager.getService("DebugPainter") as DebugPainter);
      List<DynamicBody> colliders = new List<DynamicBody>();
      collisionManager.detectObjectCollisions(this.body,
                                              beforeContact: _beforeContact,
                                              beforePenetration: _beforePenetration,
                                              colliders: colliders);
      Vector2 rayFrom = new Vector2();
      Vector2 rayTo = new Vector2();
      for (DynamicBody body in colliders) {
        if (body.data is Actor) {
          rayFrom.clone(this.body.position);
          rayTo.clone(body.collidable != null ? body.collidable.getRandomPoint(body, random) : body.position);
          
          // Draw debug info.
          if (debugPainter != null) {
            debugPainter.debugDrawLine(rayFrom,
                                       rayTo,
                                       style:"lightgray");
          }
          
          DynamicBody rayedBody = collisionManager.raycast(rayFrom, rayTo);
          
          // Draw debug info.
          if (debugPainter != null) {
            debugPainter.debugDrawLine(rayFrom,
                                       rayTo,
                                       style:"gray");
          }
          
          if (rayedBody != null && rayedBody.data is Actor && rayedBody.data.id == body.data.id) {
            this._seenActors.add(body.data.id);
          }
        }
      }
    }
    return this._seenActors;
  }
  
  bool isSeen (CollisionManager collisionManager, int id) {
    Set<int> seenActors = getSeenActors(collisionManager);
    return seenActors.contains(id);
  }
  
  bool _beforeContact (DynamicBody body1,
                      DynamicBody body2) {
    return (body1.id == this.body.id && body2.data is Actor && body2.data.id != this._actorId) ||
           (body2.id == this.body.id && body1.data is Actor && body1.data.id != this._actorId);
  }
  
  bool _beforePenetration (DynamicBody body1, 
                           DynamicBody body2) {
    // Abort, so that we don't perform unnecessary collision calculations.
    return false;
  }
}
